// Copyright (C) Kumo inc. and its affiliates.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//


#pragma once

#include <pollux/common/base/exceptions.h>
#include <pollux/common/base/gtest_macros.h>

namespace kumo::pollux::common {
    /// Utility class to represent ranges of input used by DWRF writer.This class
    /// does not dedepe overlapping ranges because for encoded input, the
    /// overlapping range should be processed the same amount of time as specified
    /// rather than just once. This class does not support representing empty
    /// ranges. It is the Caller's responsibility to avoid using the class when
    /// empty ranges are possible.
    class Ranges {
    public:
        void add(size_t begin, size_t end) {
            if (begin == end) {
                return;
            }
            POLLUX_CHECK_LT(begin, end);
            size_ += (end - begin);
            if (!ranges_.empty()) {
                // try merge with last.
                auto &last = ranges_.back();
                auto &lastEnd = std::get<1>(last);
                if (lastEnd == begin) {
                    lastEnd = end;
                    return;
                }
            }

            // add new one
            ranges_.emplace_back(begin, end);
        }

        /// Returns another instance with ranges meet the filter criteria
        Ranges filter(const std::function<bool(size_t)> &func) const;

        class Iterator {
        public:
            Iterator(
                std::vector<std::tuple<size_t, size_t> >::const_iterator cur,
                std::vector<std::tuple<size_t, size_t> >::const_iterator end)
                : end_{end}, cur_{cur}, val_{0} {
                if (cur_ != end_) {
                    val_ = std::get<0>(*cur_);
                }
            }

            bool operator==(const Iterator &other) const {
                return std::tie(cur_, end_, val_) ==
                       std::tie(other.cur_, other.end_, other.val_);
            }

            bool operator!=(const Iterator &other) const {
                return !operator==(other);
            }

            Iterator &operator++() {
                POLLUX_DCHECK(cur_ != end_);
                if (++val_ == std::get<1>(*cur_)) {
                    val_ = (++cur_ != end_ ? std::get<0>(*cur_) : 0);
                }
                return *this;
            }

            const size_t &operator*() const {
                POLLUX_DCHECK(cur_ != end_);
                return val_;
            }

        private:
            const std::vector<std::tuple<size_t, size_t> >::const_iterator end_;
            std::vector<std::tuple<size_t, size_t> >::const_iterator cur_;
            size_t val_;
        };

        Iterator begin() const {
            return Iterator{ranges_.cbegin(), ranges_.cend()};
        }

        Iterator end() const {
            return Iterator{ranges_.cend(), ranges_.cend()};
        }

        size_t size() const {
            return size_;
        }

        void clear() {
            ranges_.clear();
            size_ = 0;
        }

        const std::vector<std::tuple<size_t, size_t> > &getRanges() const {
            return ranges_;
        }

        static Ranges of(size_t begin, size_t end) {
            Ranges ranges;
            ranges.add(begin, end);
            return ranges;
        }

    private:
        std::vector<std::tuple<size_t, size_t> > ranges_;
        size_t size_{0};

        POLLUX_FRIEND_TEST(RangeTests, Add);
        POLLUX_FRIEND_TEST(RangeTests, Filter);
    };
} // namespace kumo::pollux::common
